﻿/*
 * ©2009-2010 XELF
 * http://xelf.info
 */

namespace XELF.Framework {

	/// <summary>
	/// 光線
	/// </summary>
	public partial struct Ray {
		/// <summary>
		/// 始点位置ベクトル
		/// </summary>
		public Vector3 Position;
		/// <summary>
		/// 方向ベクトル
		/// </summary>
		public Vector3 Direction;

		/// <summary>
		/// コンストラクタ
		/// </summary>
		/// <param name="position">始点位置ベクトル</param>
		/// <param name="direction">方向ベクトル</param>
		public Ray(Vector3 position, Vector3 direction) {
			Position = position;
			Direction = direction;
		}

		/// <summary>
		/// 交差を確認する。
		/// </summary>
		/// <param name="plane">平面</param>
		/// <param name="result">距離</param>
		public void Intersects(ref Plane plane, out float? result) {
			var d = plane.Dot(Direction);
			if (d == 0) {
				result = null;
				return;
			}
			result = (plane.D - plane.Dot(Position)) * MathHelper.Rcp(d);
		}

		/// <summary>
		/// 光線と三角形の交差を判定する。
		/// </summary>
		/// <param name="ray"></param>
		/// <param name="triangleA"></param>
		/// <param name="triangleB"></param>
		/// <param name="triangleC"></param>
		/// <param name="result">交差の位置と重心座標のもとになる情報が得られる。</param>
		public void Intersects(
			ref Vector3 triangleA, ref Vector3 triangleB, ref Vector3 triangleC, out RayHit result) {
			Vector3 b, c, p;
			Vector3.Subtract(ref triangleA, ref triangleB, out b);
			Vector3.Subtract(ref triangleA, ref triangleC, out c);
			Vector3.Subtract(ref triangleA, ref Position, out p);
			result = new RayHit();
			result.Denominator = Vector3.ScalarTripleProduct(ref b, ref c, ref Direction);
			result.Barycentric.Y = Vector3.ScalarTripleProduct(ref p, ref c, ref Direction);
			result.Barycentric.Z = Vector3.ScalarTripleProduct(ref b, ref p, ref Direction);
			result.Barycentric.X = result.Denominator - result.Barycentric.Y - result.Barycentric.Z;
			result.IsValid =
				result.Barycentric.X >= 0 &&
				result.Barycentric.Y >= 0 &&
				result.Barycentric.Z >= 0 &&
				result.Barycentric.X <= result.Denominator &&
				result.Barycentric.Y <= result.Denominator &&
				result.Barycentric.Z <= result.Denominator;
			result.Distance = Vector3.ScalarTripleProduct(ref b, ref c, ref p);
		}
	}

}

namespace XELF.Framework {

	/// <summary>
	/// 光線の交差情報
	/// </summary>
	public struct RayHit {
		/// <summary>
		/// 距離の分子成分
		/// </summary>
		public float Distance;
		/// <summary>
		/// 三角形の場合の重心座標の分子成分
		/// </summary>
		public Vector3 Barycentric;
		/// <summary>
		/// 分母成分
		/// </summary>
		public float Denominator;
		/// <summary>
		/// 交点が有効かどうか。
		/// 三角形の交差結果の場合には、重心座標が三角形内を指すときに有効を示し、
		/// 距離パラメータに関する負数（光源の後方）や遠方についての判定を含んでいない。
		/// </summary>
		public bool IsValid;

		/// <summary>
		/// 交点を得る。
		/// </summary>
		/// <param name="ray">光線</param>
		/// <param name="result">交点の位置ベクトル</param>
		public void GetPoint(ref Ray ray, out Vector3 result) {
			ray.GetPoint(Distance * MathHelper.Rcp(Denominator), out result);
		}
		/// <summary>
		/// 交点と重心座標を得る。
		/// </summary>
		/// <param name="ray"></param>
		/// <param name="resultPoint">交点の位置ベクトル</param>
		/// <param name="resultBarycentric">重心座標</param>
		public void GetPoint(ref Ray ray, out Vector3 resultPoint, out Vector3 resultBarycentric) {
			var r = MathHelper.Rcp(Denominator);
			ray.GetPoint(Distance * r, out resultPoint);
			Vector3.Multiply(ref Barycentric, r, out resultBarycentric);
		}
	}
}

namespace XELF.Framework {

	public partial struct Ray {

		/// <summary>
		/// 光線上の点
		/// </summary>
		/// <param name="t">パラメータ</param>
		/// <returns>位置ベクトル</returns>
		public Vector3 this[float t] {
			get {
				Vector3 result;
				Vector3.Multiply(ref Direction, t, out result);
				Vector3.Add(ref result, ref Position, out result);
				return result;
			}
		}

		/// <summary>
		/// 光線上の点
		/// </summary>
		/// <param name="t">パラメータ</param>
		/// <param name="result">位置ベクトル</param>
		public void GetPoint(float t, out Vector3 result) {
			Vector3.Multiply(ref Direction, t, out result);
			Vector3.Add(ref result, ref Position, out result);
		}

	}

}
